//
//  CPSPerformanceManager.m
//  CPSPerformanceSDK
//
//  Created by 胡中楷 on 2021/3/26.
//

#import "CPSPerformanceManager.h"
#import <UIKit/UIKit.h>
#import "mach/mach.h"
#import "CPSPerformanceView.h"
#import <CoreTelephony/CTCarrier.h>
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <net/if_dl.h>

@interface CPSPerformanceManager ()
{
    // 总网速
    uint32_t _iBytes;
    uint32_t _oBytes;
    uint32_t _allFlow;
    
    // wifi网速
    uint32_t _wifiIBytes;
    uint32_t _wifiOBytes;
    uint32_t _wifiFlow;
    
    // 3G网速
    uint32_t _wwanIBytes;
    uint32_t _wwanOBytes;
    uint32_t _wwanFlow;
}

@property (nonatomic,strong) CADisplayLink *link;
@property (nonatomic,strong) CPSPerformanceView *performanceView;
@property (nonatomic, copy, readonly) NSString *downloadNetworkSpeed;
@property (nonatomic, copy, readonly) NSString *uploadNetworkSpeed;

@end

@implementation CPSPerformanceManager

+ (instancetype)defaultManager {
    static dispatch_once_t onceToken;
    static CPSPerformanceManager *manager = nil;
    dispatch_once(&onceToken, ^{
        manager = [[CPSPerformanceManager alloc] init];
    });
    return manager;
}

- (instancetype)init
{
    if (self = [super init]) {
        _iBytes = _oBytes = _allFlow = _wifiIBytes = _wifiOBytes = _wifiFlow = _wwanIBytes = _wwanOBytes = _wwanFlow = 0;
    }
    return self;
}

- (void)startMonitoringWithView:(UIView *)view{
    [self link];
    [self.performanceView showPerformanceViewInView:view];
}

- (void)stopMonitoring{
    [_link invalidate];
    _link = nil;
    [self.performanceView hidePerformanceView];
}

- (CADisplayLink *)link {
    if (_link == nil) {
        CADisplayLink * link = [CADisplayLink displayLinkWithTarget:self selector:@selector(screenRefresh:)];
        [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
        link.preferredFramesPerSecond = 60;
        _link = link;
    }
    return _link;
}

- (void)screenRefresh:(CADisplayLink *)link {
    static CFTimeInterval lastTime = 0.0;
    static int count = 0;
    if (lastTime == 0) {
        lastTime = link.timestamp ;
        return;
    }
    count ++;
    double fps = 0;
    // 1秒内不重复计算
    if (link.timestamp - lastTime<=1.0) {
        return;
    }
    // 超过一秒了,计算下帧数
    fps = count/(link.timestamp - lastTime);
    //    NSLog(@"%lf %lf %lf %lf",fps,link.duration,link.timestamp,link.targetTimestamp);
    count = 0;
    lastTime = link.timestamp ;
    
    [self showResultWithFPS:fps];
}


- (void)showResultWithFPS:(double)fps {
    // 用透明度表明性能状态,>=100,说明可能存在卡顿 100-fps + self.cpuUsage + self.usedMemoryInMB/10
    double cpu = self.cpuUsage;
    double mem = self.usedMemoryInMB;

    [self.performanceView cps_updateShowContentFPS:fps memory:mem cpu:cpu networkFlow:[self checkNetworkSpeed]];
}

- (CGFloat)usedMemoryInMB{
    vm_size_t memory = usedMemory();
    return memory / 1024.0 / 1024.0;
}

- (CGFloat)cpuUsage{
    float cpu = cpu_usage();
    return cpu;
}

#pragma mark ------内存监测------
vm_size_t usedMemory(void) {
    
    // 这个方法得出的与xcode最接近
    task_vm_info_data_t vmInfo;
    mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
    if(task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count) != KERN_SUCCESS) {
        return 0;
    }
    return (double)vmInfo.phys_footprint;
    
    // 腾讯的GT应该是采用了这个,但是这个和xcode的相差甚远
    //    task_basic_info_data_t taskInfo;
    //    mach_msg_type_number_t infoCount = TASK_BASIC_INFO_COUNT;
    //    kern_return_t kernReturn = task_info(mach_task_self(),
    //                                         TASK_BASIC_INFO, (task_info_t)&taskInfo, &infoCount);
    //
    //    if(kernReturn != KERN_SUCCESS) {
    //        return 0;
    //    }
    //    return taskInfo.resident_size;
    
    
}

#pragma mark ------cpu监测------
float cpu_usage()
{
    kern_return_t kr;
    task_info_data_t tinfo;
    mach_msg_type_number_t task_info_count;
    
    task_info_count = TASK_INFO_MAX;
    kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)tinfo, &task_info_count);
    if (kr != KERN_SUCCESS) {
        return -1;
    }
    
    task_basic_info_t      basic_info;
    thread_array_t         thread_list;
    mach_msg_type_number_t thread_count;
    
    thread_info_data_t     thinfo;
    mach_msg_type_number_t thread_info_count;
    
    thread_basic_info_t basic_info_th;
    uint32_t stat_thread = 0; // Mach threads
    
    basic_info = (task_basic_info_t)tinfo;
    
    // get threads in the task
    kr = task_threads(mach_task_self(), &thread_list, &thread_count);
    if (kr != KERN_SUCCESS) {
        return -1;
    }
    if (thread_count > 0)
        stat_thread += thread_count;
    
    long tot_sec = 0;
    long tot_usec = 0;
    float tot_cpu = 0;
    int j;
    // for each thread
    for (j = 0; j < thread_count; j++)
    {
        thread_info_count = THREAD_INFO_MAX;
        kr = thread_info(thread_list[j], THREAD_BASIC_INFO,
                         (thread_info_t)thinfo, &thread_info_count);
        if (kr != KERN_SUCCESS) {
            return -1;
        }
        
        basic_info_th = (thread_basic_info_t)thinfo;
        
        if (!(basic_info_th->flags & TH_FLAGS_IDLE)) {
            tot_sec = tot_sec + basic_info_th->user_time.seconds + basic_info_th->system_time.seconds;
            tot_usec = tot_usec + basic_info_th->user_time.microseconds + basic_info_th->system_time.microseconds;
            tot_cpu = tot_cpu + basic_info_th->cpu_usage / (float)TH_USAGE_SCALE * 100.0;
        }
        
    }
    
    kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));
    assert(kr == KERN_SUCCESS);
    
    return tot_cpu;
}

#pragma mark ------流量监测方法------

/*获取网络流量信息*/

- (NSString *)stringWithbytes:(int)bytes {
    
    if (bytes < 1024) { // B
        return [NSString stringWithFormat:@"%dB", bytes];
    } else if (bytes >= 1024 && bytes < 1024 * 1024) { // KB
        return [NSString stringWithFormat:@"%.0fKB", (double)bytes / 1024];
    } else if (bytes >= 1024 * 1024 && bytes < 1024 * 1024 * 1024) { // MB
        return [NSString stringWithFormat:@"%.1fMB", (double)bytes / (1024 * 1024)];
    } else { // GB
        return [NSString stringWithFormat:@"%.1fGB", (double)bytes / (1024 * 1024 * 1024)];
    }
    
}

- (NSString *)checkNetworkSpeed {
    
    struct ifaddrs *ifa_list = 0, *ifa;
    if (getifaddrs(&ifa_list) == -1) return @"";
    
    uint32_t iBytes = 0;
    uint32_t oBytes = 0;
    uint32_t allFlow = 0;
    uint32_t wifiIBytes = 0;
    uint32_t wifiOBytes = 0;
    uint32_t wifiFlow = 0;
    uint32_t wwanIBytes = 0;
    uint32_t wwanOBytes = 0;
    uint32_t wwanFlow = 0;
    
    for (ifa = ifa_list; ifa; ifa = ifa->ifa_next) {
        if (AF_LINK != ifa->ifa_addr->sa_family) continue;
        if (!(ifa->ifa_flags & IFF_UP) && !(ifa->ifa_flags & IFF_RUNNING)) continue;
        if (ifa->ifa_data == 0) continue;
                
        // network
        if (strncmp(ifa->ifa_name, "lo", 2)) {
            struct if_data* if_data = (struct if_data*)ifa->ifa_data;
            iBytes += if_data->ifi_ibytes;
            oBytes += if_data->ifi_obytes;
            allFlow = iBytes + oBytes;
        }
        
        //wifi
        if (!strcmp(ifa->ifa_name, "en0")) {
            struct if_data* if_data = (struct if_data*)ifa->ifa_data;
            wifiIBytes += if_data->ifi_ibytes;
            wifiOBytes += if_data->ifi_obytes;
            wifiFlow = wifiIBytes + wifiOBytes;
        }
        
        //3G or gprs
        if (!strcmp(ifa->ifa_name, "pdp_ip0")) {
            struct if_data* if_data = (struct if_data*)ifa->ifa_data;
            wwanIBytes += if_data->ifi_ibytes;
            wwanOBytes += if_data->ifi_obytes;
            wwanFlow = wwanIBytes + wwanOBytes;
        }
    }
    
    
    freeifaddrs(ifa_list);
    
    if (_iBytes != 0) {
        _downloadNetworkSpeed = [[self stringWithbytes:iBytes - _iBytes] stringByAppendingString:@"/s"];
        NSLog(@"downloadNetworkSpeed : %@",_downloadNetworkSpeed);
    }
    
    _iBytes = iBytes;
    
    if (_oBytes != 0) {
        _uploadNetworkSpeed = [[self stringWithbytes:oBytes - _oBytes] stringByAppendingString:@"/s"];;
        NSLog(@"uploadNetworkSpeed :%@",_uploadNetworkSpeed);
    }
    
    _oBytes = oBytes;
    
    return _downloadNetworkSpeed;
    
}

#pragma mark ------CPSPerformanceView------
- (CPSPerformanceView *)performanceView{
    if(!_performanceView){
        _performanceView = [[CPSPerformanceView alloc] initWithFrame:CGRectMake(0, 44, [UIScreen mainScreen].bounds.size.width/4, 72)];
        _performanceView.backgroundColor = [UIColor redColor];
    }
    return _performanceView;
}

@end
